package library

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/go-redis/redis/v8"
	"go.uber.org/zap"
	"hhos-mdijounery-server/global"
	"hhos-mdijounery-server/library/response"
	"hhos-mdijounery-server/utils"
	"io"
	"net/http"
	"reflect"
	"sort"
	"strings"
	"time"
)

type BaseMidJourneyApi struct {
	appKey      string
	appSecret   string
	AdminUrl    string
	RedisClient *redis.Client
	Token       string
}

var (
	timeStamp = time.Now().Unix()
)

const (
	midJourneyCallback = "/midjourney/callback"  // 回调地址
	pointGetToken      = "/platform/get_token"   // 获取授权token
	aiGenerateImage    = "/mj/midjourney/imgine" // 文字生成图片
	aiChangeImage      = "/mj/midjourney/change"
)

const (
	StringMidJourneyToken   = "string_service_token"       // redis元服务token-key
	StringConcurrentLockKey = "string_concurrent_lock_key" // redis并发锁 key
)

const (
	StringConcurrentLockValue = "string_concurrent_lock_value"
)

func NewBaseMidJourneyApi() *BaseMidJourneyApi {
	return &BaseMidJourneyApi{
		appKey:    global.CONFIG.Point.AppKey,
		appSecret: global.CONFIG.Point.AppSecret,
		AdminUrl:  global.CONFIG.Point.AdminUrl,
		RedisClient: redis.NewClient(&redis.Options{
			Addr: fmt.Sprintf("%s:%d", global.CONFIG.Redis.Host, global.CONFIG.Redis.Port),
			DB:   global.CONFIG.Redis.DB,
		}),
	}
}

// GetToken 获取token
func (b *BaseMidJourneyApi) GetToken(ctx context.Context) (token string, err error) {

	//读取缓存
	token, err = b.RedisClient.Get(ctx, StringMidJourneyToken).Result()
	if err != nil && err != redis.Nil {
		global.Logger.Error("b.RedisClient.Get--StringMidjourneyToken:", zap.Error(err))
		return
	}

	if token != "" {
		return token, err
	}

	//设置 token 并发锁
	err = b.setConcurrentLock(ctx)
	if err != nil {
		return "", err
	}

	defer func(b *BaseMidJourneyApi, ctx context.Context) {
		err := b.delConcurrentLock(ctx)
		if err != nil {
			global.Logger.Error("b.delConcurrentLock-err:", zap.Error(err))
		}
	}(b, ctx)

	//组装参数
	var paramsData = map[string]string{
		"app_key":    b.appKey,
		"app_secret": b.appSecret,
	}

	url := b.makeUrl(pointGetToken)

	headers := map[string]string{
		"Content-Type": "application/json",
	}
	global.Logger.Info("HttpPost-paramsData:", zap.Any("paramsData", paramsData))
	result, err := HttpPost(url, headers, paramsData)
	global.Logger.Info("GetToken result", zap.String("result", result))
	if err != nil {
		global.Logger.Error("GetToken HttpPost--err:", zap.Error(err))
		return
	}

	var resultData response.TokenApiRes
	err = json.Unmarshal([]byte(result), &resultData)
	if err != nil {
		return
	}

	// 过期时间提前20分钟
	token = resultData.Data.Token                                        // token
	timeNowTime := time.Now().Unix()                                     // 当前时间
	diffTime := timeNowTime - time.Now().Add(-time.Minute*20).Unix()     // 20分钟秒数
	expiredTime := resultData.Data.ValidityTime - timeNowTime - diffTime // 过期时间秒数

	//设置缓存
	err = b.RedisClient.Set(ctx, StringMidJourneyToken, token, time.Duration(utils.SecondToNanosecond(expiredTime))).Err()

	if err != nil {
		global.Logger.Error("b.RedisClient.Set-err:", zap.Error(err))
		return
	}
	return
}

func (b *BaseMidJourneyApi) makeUrl(url string) string {
	return b.AdminUrl + url
}

func (b *BaseMidJourneyApi) makeCallbackUrl(url string) string {
	return global.CONFIG.HttpServer.HostUrl + url
}

func HttpPost(url string, headers map[string]string, params interface{}) (result string, err error) {

	paramsJson, err := json.Marshal(params)
	if err != nil {
		return
	}

	paramsData := bytes.NewBuffer(paramsJson)

	global.Logger.Info("HttpPost-url:", zap.String("url", url))

	req, err := http.NewRequest("POST", url, paramsData)

	// 循环设置header头
	for index, item := range headers {
		req.Header.Set(index, item)
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	global.Logger.Info("HttpPost-resp:", zap.Any("resp", resp))

	if err != nil {
		global.Logger.Error("HttpPost response-err:", zap.Error(err))
		return
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			global.Logger.Error("HttpPost Body-Close-err:", zap.Error(err))
		}
	}(resp.Body)

	// 处理响应
	if resp.StatusCode != http.StatusOK {
		global.Logger.Error("HttpPost response.StatusCode:", zap.Int("resp.statusCode:", resp.StatusCode))
		return
	}

	// 读取响应数据
	body := new(bytes.Buffer)
	_, err = body.ReadFrom(resp.Body)
	if err != nil {
		global.Logger.Error("HttpPost body.ReadFrom-err:", zap.Error(err))
		return
	}

	global.Logger.Info("resp.body.String:", zap.String("body.String", body.String()))

	// 返回数据
	return body.String(), err
}

func (b *BaseMidJourneyApi) MakeSign(params map[string]string) string {
	if params == nil {
		return strings.ToLower(utils.MD5V([]byte(fmt.Sprintf("%s=%s&%s=%d", "as", b.appSecret, "ts", timeStamp))))
	}

	// 提取字典的键到切片
	var keys []string
	for k := range params {
		keys = append(keys, k)
	}

	// 对切片进行排序
	sort.Strings(keys)

	var signStr string

	// 打印排序后的结果
	for _, item := range keys {
		// 判断是否为string
		if reflect.TypeOf(params[item]).Kind() != reflect.String {
			data, err := json.Marshal(params[item])
			if err != nil {
				global.Logger.Error("MakeSign json.Marshal-err:", zap.Error(err))
			}
			signStr += item + "=" + string(data) + "&"
		}
		signStr += item + "=" + params[item] + "&"
	}

	result := strings.TrimSpace(signStr) + fmt.Sprintf("%s=%s&%s=%d", "as", b.appSecret, "ts", timeStamp)
	return strings.ToLower(utils.MD5V([]byte(result)))
}

// setConcurrentLock 设置并发锁
func (b *BaseMidJourneyApi) setConcurrentLock(ctx context.Context) error {
	//设置获取 token 锁
	result, err := b.RedisClient.SetNX(ctx, StringConcurrentLockKey, StringConcurrentLockValue, time.Minute).Result()
	if err != nil {
		// 如果获取锁失败，是直接返回失败，还是等待重新获取
		global.Logger.Error("b.RedisClient.SetEX--StringConcurrentLockKey:", zap.Error(err))
	}

	if result == false {
		return errors.New("已设置并发锁，不能重复设置")
	}

	return err
}

// delConcurrentLock 删除并发锁
func (b *BaseMidJourneyApi) delConcurrentLock(ctx context.Context) error {
	// 删除获取 token 锁
	result, err := b.RedisClient.Del(ctx, StringConcurrentLockKey).Result()
	if err != nil {
		global.Logger.Error("b.RedisClient.Del--StringConcurrentLockKey:", zap.Error(err))
	}

	fmt.Println("result:", result)

	return err
}
